#ifndef OBJECTS_HPP
#define OBJECTS_HPP
#include <string>
#include <glm/gtx/quaternion.hpp>
#include <glm/ext/quaternion_common.hpp>
namespace glm
{
	GLM_FUNC_QUALIFIER mat4 scale2(mat4 const& m, vec3 const& v)
	{
		mat4 Result = m;
		Result[0][0] = v[0];
		Result[1][1] = v[1];
		Result[2][2] = v[2];
		return Result;
	}
}
#include "flecs.h"
#include "Model.hpp"
#include "Shader.hpp"
#include "Camera.hpp"
#include "flecs_components.hpp"


class Object3D
{
private:
	flecs::entity entity;
public:
	Object3D(const flecs::world& w, flecs::entity ecs, std::string modelPath = "")
	{
		entity = w.entity()
			.child_of(ecs)
			.set<vec3, tagPosition>(vec3(0.f))
			.set<quat, tagRotation>(quat(glm::radians(glm::vec3(0.f))))
			.set<vec3, tagScale>(vec3(1.f))
			.set<mat4>(mat4(1.f));

		if (modelPath.substr(modelPath.find_last_of('/') + 1, modelPath.length()) != "")
		{
			entity.add<Model>();
			entity.set<Model>(Model(modelPath));
		}
	}
	Object3D(std::string name, flecs::entity ecs, std::string modelPath = "")
	{
		entity = ecs.world().entity(name.c_str())
			.set<vec3, tagPosition>(vec3(0.f))
			.set<quat, tagRotation>(quat(glm::radians(glm::vec3(0.f))))
			.set<vec3, tagScale>(vec3(1.f))
			.set<mat4>(mat4(1.f));

		if (modelPath.substr(modelPath.find_last_of('/') + 1, modelPath.length()) != "")
		{
			entity.set<Model>(Model(modelPath));
		}
	}


	inline flecs::entity& getEntity()
	{
		return entity;
	}

	inline const vec3  getScale()  const
	{
		return *entity.get<vec3, tagScale>();
	}
	inline const mat4* getMatrix() const
	{
		return entity.get<mat4>();
	}

	inline const vec3  getPosition() const
	{
		return *entity.get<vec3, tagPosition>();
	}
	inline const quat  getRotation() const
	{
		return *entity.get<quat, tagRotation>();
	}

	inline virtual const glm::vec3 getDirection() const
	{
		return glm::vec3(0.f, 0.f, 1.f) * glm::toMat3(getRotation());
	}


	inline virtual void teleport(const glm::vec3& position)
	{
		glm::mat4 mat = *entity.get<mat4>();
		mat[3] = glm::vec4(position, mat[3].w);

		entity.set<mat4>(mat)
			.set<vec3, tagPosition>(position);
	}
	inline virtual void move(const glm::vec3& move)
	{
		entity.set<vec3, tagPosition>(*entity.get<vec3, tagPosition>() + move)
			.set<mat4>(glm::translate(*entity.get<mat4>(), move));
	}

	inline void rotation(const glm::vec3& rotation_in_radians)
	{
		const glm::quat& quaternion(rotation_in_radians);
		entity.set<mat4>
			(
				glm::scale
				(
					glm::translate(glm::mat4(1.f), *entity.get<vec3, tagPosition>()) * glm::toMat4(quaternion),
					*entity.get<vec3, tagScale>()
				)
			);
		entity.set<quat, tagRotation>(quaternion);
	}
	inline virtual void rotation(const quat& rotation)
	{
		entity.set<mat4>
			(
				glm::scale
				(
					glm::translate(glm::mat4(1.f), *entity.get<vec3, tagPosition>()) * glm::toMat4(rotation),
					*entity.get<vec3, tagScale>()
				)
			);
		entity.set<quat, tagRotation>(rotation);
	}
	inline void rotateBy(const glm::vec3& rotation)
	{
		auto current = glm::eulerAngles(*entity.get<quat, tagRotation>());
		const glm::quat& quaternion(current + rotation);

		entity.set<mat4>
			(
				glm::translate(glm::mat4(1.f), *entity.get<vec3, tagPosition>())
				* glm::toMat4(quaternion)
				* glm::scale(glm::mat4(1.f), *entity.get<vec3, tagScale>())
			).set<quat, tagRotation>(quaternion);
	}

	inline virtual void setDirection(const glm::vec3& target)
	{
		auto look_at = glm::lookAt(glm::vec3(0.f), -target, glm::vec3(0.f, 1.f, 0.f));

		glm::mat4 mat(glm::scale(glm::mat4(1.f), *entity.get<vec3, tagScale>()) * glm::inverse(look_at));
		mat[3] = glm::vec4(getPosition(), mat[3].w);
		entity.set<mat4>(mat).set<quat, tagRotation>(glm::toQuat(mat));
	}
	inline virtual void lookAt(const glm::vec3& target)
	{
		auto position = *entity.get<vec3, tagPosition>();
		auto look_at = glm::lookAt(-position, -target, glm::vec3(0, 1, 0));

		glm::mat4 mat(glm::scale(glm::mat4(1.f), *entity.get<vec3, tagScale>()) * glm::inverse(look_at));
		mat[3] = glm::vec4(position, mat[3].w);

		entity
			.set<mat4>(mat)
			.set<quat, tagRotation>(glm::toQuat(mat));
	}

	inline virtual void scale(const glm::vec3& scale)
	{
		auto& entity = getEntity();
		entity
			.set<vec3, tagScale>(scale)
			.set<mat4>(glm::scale2(*entity.get<mat4>(), scale));
	}


	inline void childOf(flecs::entity entity_)
	{
		entity.child_of(entity_);
	}
	inline void destroy(flecs::world ecs)
	{
		entity.clear();
		entity.destruct();
	}
};
#endif // OBJECTS_HPP